﻿using System;
/*
    This file is part of News Worm.

    News Worm is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    News Worm is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with News Worm.  If not, see <http://www.gnu.org/licenses/>.
 */

using System.Collections.Generic;
using System.Drawing;
using System.ServiceModel;
using System.Xml;
using System.IO;
using System.Text;
using System.Linq;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Threading;

namespace NewsWorm {
    internal class FeedManager : IFeedManager {
        internal FeedManager( string apphomepath, RegistryManager registry, ScriptingEvents events ) {
            m_Feeds = new List<Feed>();
            m_VisitedItems = new Dictionary<string, string>();
            m_RemovedItems = new List<string>();
            m_LastFeedUrls = new List<string>();
            m_StarredItems = new List<FavoriteItem>();
            m_LastExportPaths = new List<string>();
            m_Events = events;
            m_Path = apphomepath;

            if ( registry.IsFirstRun ) {
                try {
                    int max = Properties.Settings.Default.ArticlesToFetch;
                    Feed feed = new Feed( "http://www.newsworm.net/news.rss", max, events );
                    this.Add( feed );
                    registry.DisableFirstRun();
                } catch ( Exception ex ) {
                    new MyMessageBoxError( ex.Message, ex.StackTrace ).ShowDialog();
                }
            }

            try {
                string path = apphomepath + "urls.dat";

                if ( Properties.Settings.Default.RememberAddedFeedUrls ) {
                    if ( File.Exists( path ) ) {
                        StreamReader reader = new StreamReader( path, Encoding.Default );
                        string line;

                        while ( ( line = reader.ReadLine() ) != null )
                            m_LastFeedUrls.Add( line );

                        reader.Close();
                    }
                }

                string favpath = apphomepath + "fav.dat";

                if ( File.Exists( favpath ) ) {
                    StreamReader reader = new StreamReader( favpath, Encoding.Default );
                    string line;

                    while ( ( line = reader.ReadLine() ) != null ) {
                        string[] sep = new string[] { "||||" };
                        string[] str = line.Split( sep, StringSplitOptions.None );


                        m_StarredItems.Add( new FavoriteItem( str[0], str[1], str[2] ) );
                    }
                    reader.Close();
                }

                string lastex = apphomepath + "exports.dat";

                if ( Properties.Settings.Default.RememberLastExportPath ) {
                    if ( File.Exists( lastex ) ) {
                        StreamReader reader = new StreamReader( lastex, Encoding.Default );
                        string line;

                        while ( ( line = reader.ReadLine() ) != null ) {
                            m_LastExportPaths.Add( line );
                        }
                        reader.Close();
                    }
                }
            } catch ( Exception e ) {
                new MyMessageBoxError( e.Message, e.StackTrace ).ShowDialog();
            }
        }

        private List<Feed> m_Feeds;
        private List<String> m_RemovedItems;
        private Dictionary<string, string> m_VisitedItems;
        private List<string> m_LastFeedUrls;
        private List<FavoriteItem> m_StarredItems;
        private List<string> m_LastExportPaths;
        private string m_Path;
        private ScriptingEvents m_Events;

        public List<string> LastFeedUrls { get { return m_LastFeedUrls; } }

        public Dictionary<string, string> VisitedItems {
            get { return m_VisitedItems; }
            set { m_VisitedItems = value; }
        }

        public void Add( Feed feed ) {
            if ( !m_Feeds.Contains( feed ) )
                m_Feeds.Add( feed );
        }

        public bool Remove( Feed feed ) {
            if ( m_Feeds.Contains( feed ) ) {
                m_Feeds.Remove( feed );
                return true;
            }

            return false;
        }

        private FeedItemsCache CreateTemporaryCache( Feed feed ) {

            FeedItemsCache cache = new FeedItemsCache();

            foreach ( FeedItem i in feed.Items )
                cache.Items.Add( i );

            return cache;
        }

        private List<Feed> m_CustomSearchFeedStack;

        private void HandleCacheForNewArticlesAndRefresh( List<string> urls, Feed feed, int max, List<NonCachedItem> list ) {
            FeedItemsCache cache = CreateTemporaryCache( feed );
            feed.RefreshOrCreate( urls, feed.Url, max, m_Events );
            List<FeedItem> DiffList = cache.Items.Except( feed.Items, new FeedItemComparer() ).ToList();

            foreach ( FeedItem item in DiffList ) {
                int count = 0;
                NonCachedItem noncache = new NonCachedItem( item.ParentFeed.Title, item.ParentFeed.HasNotification );
                //How many articles with the same parent feed?
                foreach ( FeedItem PerFeed in DiffList ) {
                    if ( noncache.FeedTitle.Equals( PerFeed.ParentFeed.Title ) )
                        count++;

                    m_Events.RaiseOnNewArticle( PerFeed );
                }
                noncache.SetArticleCount( count );

                if ( !list.Contains( noncache ) )
                    list.Add( noncache );
            }
            cache = null;
        }

        //when one feed is manually refreshed we assume that the user can see what's changed
        //no unnecessary notifications
        //on sync all, custom feeds must be synced last so all the other ones are up2date(create a stack of custom feeds)
        //custom feeds should get new list of urls on refresh
        //For non-custom feeds the list of urls is ignored.
        //list have all the feed(noncustom) urls when m_CustomSearchFeedStack feeds will be refreshed
        public List<NonCachedItem> RefreshAll() {
            try {
                m_CustomSearchFeedStack = new List<Feed>();
                List<string> urls = new List<string>();
                int max = Properties.Settings.Default.ArticlesToFetch;
                List<NonCachedItem> list = new List<NonCachedItem>();

                foreach ( Feed feed in m_Feeds ) {
                    if ( !feed.IsCustomSearchFeed ) {
                        urls.Add( feed.Url );
                        HandleCacheForNewArticlesAndRefresh( urls, feed, max, list );
                    } else {
                        m_CustomSearchFeedStack.Add( feed );
                    }
                }

                foreach ( Feed feed in m_CustomSearchFeedStack ) {
                    HandleCacheForNewArticlesAndRefresh( urls, feed, max, list );
                }

                return list;
            } catch ( Exception ex ) {
                Console.Write( ex.Message );
                return null;
            }
        }


        public List<FeedItem> GetAllArticles() {
            List<FeedItem> articles = new List<FeedItem>();
            foreach ( Feed feed in m_Feeds ) {
                foreach ( FeedItem article in feed.Items ) {
                    articles.Add( article );
                }
            }
            return articles;
        }

        public List<Feed> Feeds { get { return m_Feeds; } }
        public int FeedCount {
            get {
                int count = 0;

                foreach ( Feed feed in m_Feeds ) {
                    if ( feed.DoNotLoad == false )
                        count++;
                }

                return count;
            }
        }
        public List<string> RemovedItems { get { return m_RemovedItems; } }

        public Feed GetFeedByTitle( string title ) {
            return m_Feeds.Find( delegate( Feed feed ) {
                if ( feed.Title == null )
                    return false;
                return feed.Title.Equals( title );
            } );
        }

        public Feed GetFeedByUrl( string url ) {
            return m_Feeds.Find( delegate( Feed feed ) {
                return feed.Url.Equals( url );
            } );
        }

        public FeedItem GetFeedItemByUrl( string url ) {
            foreach ( Feed feed in m_Feeds ) {
                foreach ( FeedItem item in feed.Items ) {
                    if ( item.Url.Equals( url ) )
                        return item;
                }
            }

            return null;
        }

        public FavoriteItem GetFavoriteByUrl( string url ) {
            foreach ( FavoriteItem item in m_StarredItems )
                if ( item.Url.Equals( url ) )
                    return item;

            return null;
        }

        public bool PreserveFeeds( string path ) {
            try {
                string FeedsFile = path + "feeds.xml";

                using ( XmlWriter writer = XmlWriter.Create( FeedsFile ) ) {
                    writer.WriteStartDocument();
                    writer.WriteStartElement( "Feeds" );
                    foreach ( Feed feed in m_Feeds ) {
                        if ( !feed.IsCustomSearchFeed ) {
                            writer.WriteStartElement( "Feed" );
                            writer.WriteElementString( "Url", feed.Url );
                            writer.WriteElementString( "Title", feed.Title );
                            writer.WriteElementString( "Description", feed.Description );
                            writer.WriteElementString( "DoNotLoad", feed.DoNotLoad.ToString() );
                            writer.WriteElementString( "IsCustomFeed", feed.IsCustomSearchFeed.ToString() );
                            writer.WriteElementString( "HasNotification", feed.HasNotification.ToString() );
                            writer.WriteStartElement( "Items" );
                            foreach ( FeedItem item in feed.Items ) {
                                writer.WriteStartElement( "Item" );
                                writer.WriteElementString( "Url", item.Url );
                                writer.WriteElementString( "Title", item.Title );
                                writer.WriteElementString( "Published", item.Published );
                                writer.WriteElementString( "Summary", item.Summary );
                                if ( !m_VisitedItems.ContainsKey( item.Url ) )
                                    writer.WriteElementString( "Seen", "false" );
                                else
                                    writer.WriteElementString( "Seen", "true" );
                                writer.WriteEndElement(); //Item
                            }
                            writer.WriteEndElement(); //Items
                            writer.WriteEndElement(); //Feed
                        }
                    }
                    writer.WriteEndElement(); //Feeds
                    writer.WriteEndDocument();
                }
                PreverveCustomFeeds( path );
                return true;
            } catch ( Exception e ) {
                throw e;
            }
        }
        //File format: title,searchterm,url;url;url
        private void PreverveCustomFeeds( string path ) {
            try {
                string CustomFeedsFile = path + "customfeeds.dat";
                StreamWriter CustomWriter = new StreamWriter( CustomFeedsFile, false, Encoding.Default );
                foreach ( Feed feed in m_Feeds ) {
                    if ( feed.IsCustomSearchFeed ) {
                        CustomWriter.Write( feed.CombinedSearchFeed.Title + "||||" + feed.CombinedSearchFeed.SearchTerm + "||||" +
                            feed.CombinedSearchFeed.UrlGuid + "||||" + feed.HasNotification.ToString() + "||||" );
                        foreach ( string url in feed.CombinedSearchFeed.Urls ) {
                            CustomWriter.Write( url + ";" );
                        }
                        CustomWriter.WriteLine();
                    }
                }
                CustomWriter.Close();
            } catch ( Exception e ) {
                throw e;
            }
        }

        public bool PreserveState( string path ) {
            try {
                string RemovedItemsFile = path + "removed.dat";

                PreserveFeeds( path );

                StreamWriter removedWriter = new StreamWriter( RemovedItemsFile, false, Encoding.Default );

                foreach ( string item in this.m_RemovedItems )
                    removedWriter.WriteLine( item );

                removedWriter.Close();

                return true;
            } catch ( Exception e ) {
                throw e;
            }
        }

        //Only load url and create new updated feed.
        //Also check if the article was read
        public static FeedManager Load( IMainWindowClient client, string path, RegistryManager registry, ScriptingEvents events ) {
            FeedManager manager = new FeedManager( path, registry, events );
            string FeedsFile = path + "feeds.xml";
            if ( File.Exists( FeedsFile ) ) {
                XDocument doc = XDocument.Load( FeedsFile );
                foreach ( XElement root in doc.Root.Elements() ) {
                    bool DoNotLoad = false;
                    bool DoNotify = true;
                    //To provide backwards compability, check that the feeds.xml
                    //has DoNotLoad element 
                    XElement load = root.Element( "DoNotLoad" );
                    XElement dn = root.Element( "HasNotification" );

                    if ( load != null )
                        DoNotLoad = Convert.ToBoolean( load.Value );

                    if ( dn != null )
                        DoNotify = Convert.ToBoolean( dn.Value );

                    if ( DoNotLoad == false ) {
                        Feed feed = null;
                        string url = root.Element( "Url" ).Value;
                        feed = new Feed( url, root.Element( "Title" ).Value, root.Element( "Description" ).Value );
                        foreach ( XElement items in root.Elements() ) {
                            foreach ( XElement item in items.Elements() ) {
                                FeedItem feeditem = null;
                                feeditem = new FeedItem( item.Element( "Url" ).Value,
                                    item.Element( "Title" ).Value, item.Element( "Published" ).Value,
                                    item.Element( "Summary" ).Value, feed );
                                string seen = item.Element( "Seen" ).Value;
                                if ( seen.Equals( "true" ) ) {
                                    if ( !manager.VisitedItems.ContainsKey( item.Element( "Url" ).Value ) ) {
                                        manager.VisitedItems.Add( item.Element( "Url" ).Value,
                                            item.Element( "Title" ).Value );//check that there are no dubs
                                    }
                                }
                                feed.AddItem( feeditem );
                            }

                        }

                        feed.HasNotification = DoNotify;
                        manager.Add( feed );
                    }
                }
            }

           /* List<Feed> CustomSearchFeeds = GetCustomSearchFeeds( path );

            foreach ( Feed f in CustomSearchFeeds )
                manager.Add( f );*/

            new Thread( delegate() { GetCustomSearchFeedsThread( client, manager, path, events ); } ).Start();
            
            return manager;
        }

        private static void GetCustomSearchFeedsThread( IMainWindowClient client, FeedManager manager ,string path, ScriptingEvents events ) {
            client.SetSyncIsOnGoingFlag( true );
            client.SetRightStatusTextThreadSafe( "Loading custom feeds..." );
            List<Feed> CustomSearchFeeds = GetCustomSearchFeeds( path,events );

            foreach ( Feed f in CustomSearchFeeds )
                manager.Add( f );

            client.RefreshFeedDisplayThreadSafe();
            client.SetRightStatusTextThreadSafe( " " );
            client.SetSyncIsOnGoingFlag( false);
        }

        //This method is only called on News Worm startup when custom feeds are loaded.
        //On the runtime Feed::GetFeedAdjusterForStdFeedByUrl is used for getting the fetch count
        private static int GetCustomsearchFeedFetchCount(string guid){

            int max = 0;

            string file = Variables.BasePath + "override.dat";
      
            if ( File.Exists( file ) ) {
                StreamReader reader = new StreamReader( Variables.BasePath + "override.dat" );
                string line;

                while ( ( line = reader.ReadLine() ) != null ) {
                    string[] sep = new string[] { "||||" };
                    string[] str = line.Split( sep, StringSplitOptions.None );
                    //str[0] url, str[1] override count
                    if ( str[0].Equals( guid ) ) {
                        max = Int32.Parse( str[1] );
                    }
                }

                reader.Close();
            }

            return max;
        }

        private static List<Feed> GetCustomSearchFeeds(string path, ScriptingEvents events) {
            
            List<Feed> feeds = new List<Feed>();
            try {
                string CustomFeedsFile = path + "customfeeds.dat";
          
                if ( File.Exists( CustomFeedsFile ) ) {
                    StreamReader reader = new StreamReader( CustomFeedsFile, Encoding.Default );
                    string line;
                    string[] sep = new string[] { "||||" };
                   
                    while ( ( line = reader.ReadLine() ) != null ) {
                        string[] str = line.Split( sep, StringSplitOptions.None );
                        string title = str[0];
                        string search = str[1];
                        string guid = str[2];
                        bool notify = Convert.ToBoolean(str[3]);
                        string[] urls = str[4].Split( ';' );
                        //0=title 1=SearchTerm 2=guid 3=notification, 4=array of urls separated by ;
                        List<string> ListOfUrls = new List<string>();
                        foreach ( string url in urls ) {
                            if(url != "")
                                ListOfUrls.Add( url );
                        }

                        int max = 0;
                        max = GetCustomsearchFeedFetchCount( guid );
                 
                        CombinedSearchFeed searchFeed = new CombinedSearchFeed( ListOfUrls, search, title, max, guid, events );
                        Feed feed = new Feed( searchFeed );
                        feed.HasNotification = notify;
                        feeds.Add( feed );
                    }

                    reader.Close();
                }
            } catch ( Exception e ) {
                Console.WriteLine( e.Message );
            }
            return feeds;
        }

        public void ReloadRemovedItems( string path ) {
            string RemovedItemsFile = path + "removed.dat";

            if ( File.Exists( RemovedItemsFile ) ) {
                StreamReader reader = new StreamReader( RemovedItemsFile, Encoding.Default );
                string line;

                while ( ( line = reader.ReadLine() ) != null ) {
                    this.m_RemovedItems.Add( line );
                }

                reader.Close();
            }
        }
        
        public void SaveUrlHistory( string path ) {
            string urlspath = path + "urls.dat";

            if ( Properties.Settings.Default.RememberAddedFeedUrls ) {
                try {
                    StreamWriter writer = new StreamWriter( urlspath, false, Encoding.Default );

                    foreach ( string url in m_LastFeedUrls )
                        writer.WriteLine( url );

                    writer.Close();
                } catch ( Exception e ) {
                    new MyMessageBoxError( e.Message, e.StackTrace ).ShowDialog();
                }
            }
        }

        public void SaveFavoriteHistory( string path ) {
            try {
                string favpath = path + "fav.dat";

                StreamWriter writer = new StreamWriter( favpath, false, Encoding.Default );

                foreach ( FavoriteItem item in m_StarredItems )
                    writer.WriteLine( item.Url + "||||" + item.Title + "||||" +
                        Regex.Replace( item.Summary, @"\r\n?|\n", " " ) );
                //Regex is used to replace any newline characters. 
                //They would break our parsing.

                writer.Close();
            } catch ( Exception e ) {
                new MyMessageBoxError( e.Message, e.StackTrace ).ShowDialog();
            }
        }

        public List<FavoriteItem> StarredItems {
            get { return m_StarredItems; }
        }

        public List<string> LastExportPaths {
            get { return m_LastExportPaths; }
        }

        public void SaveExportPaths( string path ) {
            string epath = path + "exports.dat";

            if ( Properties.Settings.Default.RememberLastExportPath ) {
                try {
                    StreamWriter writer = new StreamWriter( epath, false, Encoding.Default );

                    foreach ( string str in m_LastExportPaths )
                        writer.WriteLine( str );

                    writer.Close();
                } catch ( Exception e ) {
                    new MyMessageBoxError( e.Message, e.StackTrace ).ShowDialog();
                }
            }
        }
    }
}
